﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using STDTO.DataTransferObjects.Interfaces;
using STDTO.DataTransferObjects.Impl;
using STDTO.Interfaces;
using System.Runtime.Serialization;


namespace STDTO.DataTransferObjects.Base
{
    [Serializable]
    public abstract class SelfTrackingDataTransferObjectBase<T> : INotifyPropertyChangedWithHasHandlers, IDataErrorInfo, ISelfTrackingDataTransferObjectEx<T>
    {
        #region Members
        //holds the modify properties
        private Dictionary<string, PropertyValuesHolder> m_modifiedProperties = null;
        //holds the state
        private DataTransferObjectState _state = DataTransferObjectState.Unchanged;
        #endregion

        # region Properties
        public DataTransferObjectState State
        {
            get { return _state; }
            set { _state = value; }
        }
        #endregion

        #region Ctor
        public SelfTrackingDataTransferObjectBase()
        {
            m_modifiedProperties = new Dictionary<string, PropertyValuesHolder>();
            m_childObjects = new List<INotifyPropertyChanged>();
        }
        #endregion

        #region Reconnect  events
        private List<INotifyPropertyChanged> m_childObjects = null;
        [OnDeserialized]
        private void ReconnectEvents(StreamingContext context)
        {
            foreach (INotifyPropertyChangedWithHasHandlers item in m_childObjects)
            {
                if (!item.HasHandlers)
                {
                    item.PropertyChanged += new PropertyChangedEventHandler(DataTransferObjectBase_PropertyChanged);
                }
            }
        }

        public bool HasHandlers
        {
            get
            {
                return PropertyChanged != null;
            }
        }
        #endregion

        #region Protected method for recursion calls
        protected IList<TInterface> CreateCollectionDtoInner<TInterface>(IList<TInterface> entityList, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            var collectonDto = SelfTrackingDataTransferObjectCollection<TInterface>.CreateCollectionDto<TInterface>(entityList, dataTrasferObjectToEntityMapper,GetDtoInstanceCreator());
            ((INotifyPropertyChanged)collectonDto).PropertyChanged += new PropertyChangedEventHandler(DataTransferObjectBase_PropertyChanged);
            m_childObjects.Add((INotifyPropertyChanged)collectonDto);
            return collectonDto;
        }
        protected TInterface CreateDtoInner<TInterface>(TInterface entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            object dtoObj;
            if (dataTrasferObjectToEntityMapper.TryGetDataTransferObject(entity, out dtoObj))
            {
                return (TInterface)dtoObj;
            }

            IDtoInstanceCreator dtoInstanceCreator = GetDtoInstanceCreator();
            ISelfTrackingDataTransferObjectEx<TInterface> res = dtoInstanceCreator.CreateDtoInstance<TInterface>(entity) as ISelfTrackingDataTransferObjectEx<TInterface>;
            dataTrasferObjectToEntityMapper.AddMapping(res, entity);
            res.Populate(entity, dataTrasferObjectToEntityMapper);
            ((INotifyPropertyChanged)res).PropertyChanged += new PropertyChangedEventHandler(DataTransferObjectBase_PropertyChanged);
            m_childObjects.Add((INotifyPropertyChanged)res);
            return (TInterface)res;
        }
        #endregion

        #region protected helper functions
        protected void Set<TProp>(string propertyName, ref TProp curMember, TProp newValue)
        {
            ProperyValueChanging(propertyName, curMember, newValue);
            curMember = newValue;
            NotifyPropertyChanged(propertyName);
            State = DataTransferObjectState.Modified;
        }

        protected TProp RetrievePropertyValue<TProp>(string propertName, bool getOriginalValue)
        {
            TProp res = default(TProp);
            if (m_modifiedProperties.ContainsKey(propertName) && getOriginalValue)
            {
                res = GetObjectOrDefault<TProp>(m_modifiedProperties[propertName].OldValue);
            }
            else
            {
                res = GetObjectOrDefault<TProp>(this.GetType().GetProperty(propertName).GetValue(this, null));
            }

            return res;
        }

        private TProp GetObjectOrDefault<TProp>(object propertyValue)
        {
            return propertyValue != null ? (TProp)propertyValue : default(TProp);
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        void DataTransferObjectBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            State = DataTransferObjectState.Modified;
            //fires the event in case father object is register to it
            NotifyPropertyChanged(e.PropertyName);
        }


        #endregion

        #region IDataTransferObjectEx<T> Members

        public void Populate(T entity)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            dataTrasferObjectToEntityMapper.AddMapping(this, entity);
            Populate(entity, dataTrasferObjectToEntityMapper);
        }

        public void Populate(T entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            SpecificPopulate(entity, dataTrasferObjectToEntityMapper);
        }

        public T Clone(bool getOriginalValue)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            return Clone(getOriginalValue, dataTrasferObjectToEntityMapper);
        }

        public T Clone(bool getOriginalValue, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            object entity = null;
            if (dataTrasferObjectToEntityMapper.TryGetEntity(this, out entity))
            {
                return (T)entity;
            }
            T res = GetServerSideInstance();
            dataTrasferObjectToEntityMapper.AddMapping(this, res);
            SpecificGetValue(res, getOriginalValue, dataTrasferObjectToEntityMapper);

            return res;
        }

        public void ApplyChanges(T entity)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            ApplyChanges(entity, dataTrasferObjectToEntityMapper);
        }

        public void ApplyChanges(T entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            object temp = null;
            //we allready applay changes
            if (dataTrasferObjectToEntityMapper.TryGetDataTransferObject(entity, out temp))
            {
                return;
            }
            //if the state is not nuchange
            if (State != DataTransferObjectState.Unchanged)
            {
                foreach (string propertyName in m_modifiedProperties.Keys)
                {
                    ApplyPropertyChange(propertyName, entity);
                }
                dataTrasferObjectToEntityMapper.AddMapping(this, entity);
                SpecificApplyChanges(entity, dataTrasferObjectToEntityMapper);

            }
        }

        #region private methods
        private void ApplyPropertyChange(string propertyName, T entity)
        {
            object newValue = m_modifiedProperties[propertyName].NewValue;
            entity.GetType().GetProperty(propertyName).SetValue(entity, newValue, null);
        }

        private void ProperyValueChanging(string propertyName, object oldValue, object newValue)
        {
            if (m_modifiedProperties.ContainsKey(propertyName))
            {
                m_modifiedProperties[propertyName].NewValue = newValue;
            }
            else
            {
                m_modifiedProperties.Add(propertyName, new PropertyValuesHolder() { OldValue = oldValue, NewValue = newValue });
            }
        }
        #endregion
        #endregion

        #region Abstract and Virtual Methods  - Override to change the current behavior
        protected abstract void SpecificPopulate(T entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper);
        protected abstract void SpecificGetValue(T entity, bool getOriginalValue, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper);
        protected abstract void SpecificApplyChanges(T entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper);
        protected abstract T GetServerSideInstance();
        protected abstract IDtoInstanceCreator GetDtoInstanceCreator();
        protected virtual IDataTrasferObjectToEntityMapper GetDataTrasferObjectToEntityMapper()
        {
            return new DataTrasferObjectToEntityMapper();
        }
        
        
        #endregion

        #region IDataErrorInfo Members - Not  implemented in the base class

        public virtual string Error
        {
            get { return string.Empty; }
        }

        public virtual  string this[string columnName]
        {
            get { return string.Empty; }
        }
        #endregion
    }
}
